#!/usr/bin/env python
# encoding: utf-8


"""
@file: sanjiaoxingqiuzhi.py
@time: 2017/1/12 上午10:32
"""
# 在三角形中，给出一个或者两个条件，让你求表达式的值。Noet：如果给出三个条件那么可以求出所有关于三角形的值了
from functools import reduce
from mathsolver.functions.base import *
from sympy import expand_trig, sin, cos, symbols, acos
from mathsolver.functions.budengshi import common_opers as co
from mathsolver.functions.sanjiao import sanjiaoxing_utils as san_utils
from itertools import chain
from mathsolver.functions.sanjiao.triangle_condition_converts import condition_converts
from mathsolver.functions.sanjiao.triangle_question_converts import question_convets
from mathsolver.functions.sanjiao.triangle_cond_action_funcs import one_cond_action_funcs, two_cond_action_funcs
import copy
from mathsolver.functions.sanjiao.triangle_structs import TriangleKnowledgeElement

side_a, side_b, side_c = symbols('a, b, c')

MAX_TRY_TIME = 20


# 已知一个条件的
# 在△ABC中,a^{2}+b^{2}=\\sqrt{3}ab+c^{2},则∠C=().
class SanJiaoXingQiuZhi001(BaseFunction):
    def solver(self, *args):
        self.label.add('三角恒等变换求值')
        arg0 = args[0]
        if len(arg0.sympify()) != 1:
            raise Exception('Type Match Error')
        l_expr, r_expr = arg0.sympify()[0]
        eq_f = l_expr - r_expr
        self.steps.append(['\\because ' + BaseEq([l_expr, r_expr]).printing(), ''])
        cos_a, cos_b, cos_c = None, None, None
        if solve(eq_f, side_a ** 2 + side_b ** 2 - side_c ** 2):
            cos_c = solve(eq_f, side_a ** 2 + side_b ** 2 - side_c ** 2)[0] / (2 * side_a * side_b)
            self.steps.append(['\\therefore ' + BaseEq(['cos(C)', cos_c]).printing(), ''])
        elif solve(eq_f, side_a ** 2 + side_c ** 2 - side_b ** 2):
            cos_b = solve(eq_f, side_a ** 2 + side_c ** 2 - side_b ** 2)[0] / (2 * side_a * side_c)
            self.steps.append(['\\therefore ' + BaseEq(['cos(B)', cos_b]).printing(), ''])
        elif solve(eq_f, side_b ** 2 + side_c ** 2 - side_a ** 2):
            cos_a = solve(eq_f, side_b ** 2 + side_c ** 2 - side_a ** 2)[0] / (2 * side_b * side_c)
            self.steps.append(['\\therefore ' + BaseEq(['cos(A)', cos_a]).printing(), ''])
        else:
            raise Exception('Type Match Error')
        if cos_a:
            eq_f1 = (sympify('cos(A)') - cos_a).subs(cos('A'), 'CA').subs(sin('A'), 'SA')
            self.steps.append(['\\because ' + BaseEq(['sin(A)**2 + cos(A)**2', '1']).printing(), ''])
            eq_f2 = sympify('CA') ** 2 + sympify('SA') ** 2 - 1
            v_as = co.solve_poly_eqs(BaseEqs([[eq_f1, '0'], [eq_f2, '0']]))
            if len(v_as) == 1:
                angle_a = acos(v_as[0][0][1])
                self.steps.append(['\\therefore ', '\\angle A = ' + new_latex(angle_a)])
                self.output.append(BaseSymbolValue({'A': angle_a}))
            else:
                angle_as = list(map(lambda v_a: acos(v_a[0][1]), v_as))
                self.steps.append(['\\therefore ', '\\angle A = ' + new_latex(angle_as[0]) + '或者' + new_latex(angle_as[1])])
                self.output.append(BaseSymbolValue({'A': angle_as}))
        elif cos_b:
            eq_f1 = (sympify('cos(B)') - cos_b).subs(cos('B'), 'CB').subs(sin('B'), 'SB')
            self.steps.append(['\\because ' + BaseEq(['sin(B)**2 + cos(B)**2', '1']).printing(), ''])
            eq_f2 = sympify('CB') ** 2 + sympify('SB') ** 2 - 1
            v_bs = co.solve_poly_eqs(BaseEqs([[eq_f1, '0'], [eq_f2, '0']]))
            v_bs = list(filter(lambda s_vs: s_vs[1][1] > 0, v_bs))
            if len(v_bs) == 1:
                angle_b = acos(v_bs[0][0][1])
                self.steps.append(['\\therefore ', '\\angle B = ' + new_latex(angle_b)])
                self.output.append(BaseSymbolValue({'B': angle_b}))
            else:
                angle_bs = list(map(lambda v_b: acos(v_b[0][1]), v_bs))
                self.steps.append(['\\therefore ', '\\angle B = ' + new_latex(angle_bs[0]) + '或者' + new_latex(angle_bs[1])])
                self.output.append(BaseSymbolValue({'B': angle_bs}))
        elif cos_c:
            eq_f1 = (sympify('cos(C)') - cos_c).subs(cos('C'), 'CC1').subs(sin('C'), 'SC')
            self.steps.append(['\\because ' + BaseEq(['sin(C)**2 + cos(C)**2', '1']).printing(), ''])
            eq_f2 = sympify('CC1') ** 2 + sympify('SC') ** 2 - 1
            v_cs = co.solve_poly_eqs(BaseEqs([[eq_f1, '0'], [eq_f2, '0']]))
            if len(v_cs) == 1:
                angle_c = acos(v_cs[0][0][1])
                self.steps.append(['\\therefore ', '\\angle C = ' + new_latex(angle_c)])
                self.output.append(BaseSymbolValue({'C': angle_c}))
            else:
                angle_cs = list(map(lambda v_c: acos(v_c[0][1]), v_cs))
                self.steps.append(['\\therefore ', '\\angle C = ' + new_latex(angle_cs[0]) + '或者' + new_latex(angle_cs[1])])
                self.output.append(BaseSymbolValue({'C': angle_cs}))
        return self


# 在△ABC中,cosA=\\frac{4}{5},tanB=2,则tan(2A+2B)的值为()
class SanJiaoXingQiuZhi002(BaseFunction):
    def solver(self, *args):
        self.label.add('三角恒等变换求值')

        def trig_subs(f):
            return f.subs(sin('A'), 'X1').subs(cos('A'), 'X2').subs(sin('B'), 'Y1'). \
                subs(cos('B'), 'Y2').subs(sin('C'), 'Z1').subs(cos('C'), 'Z2')

        def rever_trig_subs(f):
            return f.subs('X1', sin('A')).subs('X2', cos('A')).subs('Y1', sin('B')). \
                subs('Y2', cos('B')).subs('Z1', sin('C'), ).subs('Z2', cos('C'))

        arg0 = args[0]
        if len(arg0.sympify()) != 2:
            raise Exception('Type Match Error')
        src_target_f = args[1].sympify()
        target_f = src_target_f
        eq1, eq2 = arg0.sympify()
        eq_f1 = eq1[0] - eq1[1]
        symbs1 = list(eq_f1.free_symbols)
        eq_f2 = eq2[0] - eq2[1]
        symbs2 = list(eq_f2.free_symbols)
        symbs = map(lambda symb: sympify(str(symb).upper()), list(set(chain(symbs1, symbs2))))
        self.steps.append(['\\because ' + arg0.printing(), ''])
        eqs2 = map(lambda symb: [sin(symb) ** 2 + cos(symb) ** 2, sympify('1')], symbs)
        self.steps.append(['又 \\because ' + BaseEqs(eqs2).printing(), ''])
        eqs2_fs = map(trig_subs, map(lambda tmp_eq: tmp_eq[0] - tmp_eq[1], eqs2))
        eqs2 = map(lambda tmp_f: [tmp_f, '0'], eqs2_fs)
        eq_f1 = co.complete_fraction(trig_subs(san_utils.tan_subs(eq1[0] - eq1[1])))[0]
        eq_f2 = co.complete_fraction(trig_subs(san_utils.tan_subs(eq2[0] - eq2[1])))[0]
        new_eqs = BaseEqs(list(chain([[eq_f1, '0'], [eq_f2, '0']], eqs2)))
        vs_list = co.solve_poly_eqs(new_eqs)
        vs_list = list(filter(lambda vs: reduce(lambda v1, v2: v1[1] > 0 and v2[1] > 0, list(filter(lambda tmp_v: str(tmp_v[0]).find('1') >= 0, vs))), vs_list))
        if len(vs_list) == 1:
            tmp_vs = list(map(lambda tmp_v: [rever_trig_subs(tmp_v[0]), tmp_v[1]], vs_list[0]))
            self.steps.append(['\\therefore ', BaseEqs(tmp_vs).printing()])
        target_f = trig_subs(san_utils.tan_subs(expand_trig(target_f)))
        target_f_vs = list(map(lambda vs: target_f.subs(vs), vs_list))
        if not target_f_vs[0].is_real:
            raise Exception('Type Match Error')
        if len(target_f_vs) == 1:
            self.steps.append(['故 %s = %s' % (new_latex(src_target_f), new_latex(target_f_vs[0])), ''])
        self.output.append(BaseSymbolValue({src_target_f: target_f_vs}))
        return self


def condition_deep_deduce(condition):
    """
    条件深度推导 
    :param condition: 是一个BaseEq
    :return:
    >>> condition_deep_deduce(BaseEq(['b**2', 'a*c']))
    [[b**2, a*c], [-sin(A)*sin(C) + sin(B)**2, '0'], [-(sin(B)*cos(C) + sin(C)*cos(B))*sin(C) + sin(B)**2, '0'], [(sin(A)*cos(C) + sin(C)*cos(A))**2 - sin(A)*sin(C), '0'], [-(sin(A)*cos(B) + sin(B)*cos(A))*sin(A) + sin(B)**2, '0'], [-cos(B)/2 - cos(2*B)/2 - cos(A - C)/2 + 1/2, 0], [sin(B)**2 - cos(B)/2 - cos(A - C)/2 - 1/2, -1/2]]
    """

    def _condition_list_eqed(cond_list1, cond_list2):
        """
        两个列表是否相等
        :param cond_list1: 
        :param cond_list2: 
        :return: 
        """
        if len(cond_list1) != len(cond_list2):
            return False
        cond_f_list1 = map(lambda pair: sympify(pair[0]) - sympify(pair[1]), cond_list1)
        cond_f_list2 = map(lambda pair: sympify(pair[0]) - sympify(pair[1]), cond_list2)
        for cond_f in cond_f_list1:
            if cond_f not in cond_f_list2:
                return False
        return True

    def _haved_condition(cond_list, cond):
        """
        列表中是否含有指定的元素
        :param cond_list: 
        :param cond: 
        :return: 
        """
        cond_f_list = map(lambda pair: sympify(pair[0]) - sympify(pair[1]), cond_list)
        cond_f = sympify(cond[0]) - sympify(cond[1])
        for tmp_cond_f in cond_f_list:
            if tmp_cond_f == cond_f:
                return True
        return False

    first_eq = list(condition.sympify())
    first_kd = TriangleKnowledgeElement(condition)  # 第一个KD输入输出是相同的
    first_kd.set_output(condition)
    prev_eqs = [first_eq, ]
    next_eqs = [first_eq, ]
    next_kds = [first_kd, ]
    firsted = True
    convered_eqs = []  # 已经转换过的等式
    while firsted or (not _condition_list_eqed(prev_eqs, next_eqs)):
        firsted = False
        prev_eqs = copy.copy(next_eqs)
        prev_kds = [kd for kd in next_kds]
        for eq_condition, kd in zip(prev_eqs, prev_kds):
            if _haved_condition(convered_eqs, eq_condition):
                continue
            deduce_eqs, kds = condition_converts(kd)
            for deduce_eq, kd2 in zip(deduce_eqs, kds):
                if not _haved_condition(next_eqs, deduce_eq):
                    next_eqs.append(deduce_eq)
                    next_kds.append(kd2)
            convered_eqs.append(eq_condition)  # 如果已经转换过 那么下次 就不用再转换了
    return prev_eqs, next_kds


# condition_deep_deduce的优化版本
def condition_deep_deduce_v2(condition):
    """
    条件深度推导 
    :param condition: 是一个BaseEq
    :return:
    >>> condition_deep_deduce(BaseEq(['b**2', 'a*c']))
    [[b**2, a*c], [-sin(A)*sin(C) + sin(B)**2, '0'], [-(sin(B)*cos(C) + sin(C)*cos(B))*sin(C) + sin(B)**2, '0'], [(sin(A)*cos(C) + sin(C)*cos(A))**2 - sin(A)*sin(C), '0'], [-(sin(A)*cos(B) + sin(B)*cos(A))*sin(A) + sin(B)**2, '0'], [-cos(B)/2 - cos(2*B)/2 - cos(A - C)/2 + 1/2, 0], [sin(B)**2 - cos(B)/2 - cos(A - C)/2 - 1/2, -1/2]]
    """

    def _haved_condition(cond_list, cond):
        """
        列表中是否含有指定的元素
        :param cond_list: 
        :param cond: 
        :return: 
        """
        cond_f_list = map(lambda pair: sympify(pair[0]) - sympify(pair[1]), cond_list)
        cond_f = sympify(cond[0]) - sympify(cond[1])
        for tmp_cond_f in cond_f_list:
            if tmp_cond_f == cond_f:
                return True
        return False

    first_eq = list(condition.sympify())
    first_kd = TriangleKnowledgeElement(condition)  # 第一个KD输入输出是相同的
    first_kd.set_output(condition)
    converting_queue = [(first_eq, first_kd), ]  # 待转换的队列
    convered_eqs = []  # 已经转换过的等式
    result_eqs = []  # 结果等式
    result_kds = []  # 结果转换知识元
    try_count = 0
    while converting_queue and try_count <= MAX_TRY_TIME:  # 直到不能转换为止
        cur_eq, cur_kd = converting_queue.pop()
        if _haved_condition(convered_eqs, cur_eq):  # 如果已经转换过
            continue
        deduce_eqs, kds = condition_converts(cur_kd)  # 转换后的表达式
        for deduce_eq, kd in zip(deduce_eqs, kds):
            if not _haved_condition(result_eqs, deduce_eq):
                result_eqs.append(deduce_eq)
                result_kds.append(kd)
        converting_queue.extend(zip(deduce_eqs, kds))  # 待转换
        convered_eqs.append(cur_eq)  # 转换一个就添加到已转换里面去
        try_count += 1
    return result_eqs, result_kds


def extract_kd_steps_and_labels(kd):
    """
    提取TriangleKnowledgeElement里面的步骤
    :param kd: 
    :return: 
    """
    _steps = []
    _labels = []
    tmp_kd = kd
    while tmp_kd:
        _steps.extend(tmp_kd.get_steps())
        _labels.extend(tmp_kd.get_labels())
        tmp_kd = tmp_kd.get_parent()
    return _steps, _labels  # 因为是反推的所以要倒过来


def extract_af_steps_and_labels(af):
    """
    提取ActionFunc里面的步骤
    :param af: 
    :return: 
    """
    return af.get_steps(), af.get_labels()


# 三角形给值求值，已知两个条件求值得[组合自动推理]
class SanJiaoXingQiuZhi003(BaseFunction):
    def solver(self, *args):
        self.label.add('解三角形')
        cond_eqs = args[0].sympify()
        if len(cond_eqs) != 2:
            raise Exception('Type Match Error')
        question_f = args[1].sympify()
        question_f = question_f.subs(san_utils.get_triangle_subs())
        question_eq_converts = question_convets(question_f)
        eq1 = cond_eqs[0]
        eq2 = cond_eqs[1]

        cond1_eq_converts, cond1_kds = condition_deep_deduce(BaseEq(eq1))
        cond2_eq_converts, cond2_kds = condition_deep_deduce(BaseEq(eq2))
        cond_eq_combins = co.combin_iters(cond1_eq_converts, cond2_eq_converts)
        cond_kd_combins = co.combin_iters(cond1_kds, cond2_kds)
        finished = False
        result = None
        action_result_dict = {}
        steps = []  # 推理的步骤
        labels = []
        for (convert_eq1, convert_eq2), (kd1, kd2) in zip(cond_eq_combins, cond_kd_combins):
            if finished:
                break
            for action_func in two_cond_action_funcs:
                if finished:
                    break
                try:
                    action_result, af = action_func(BaseEqs([convert_eq1, convert_eq2]))
                    if not action_result:
                        continue
                    kd1_steps, kd1_labels = extract_kd_steps_and_labels(kd1)
                    kd2_steps, kd2_labels = extract_kd_steps_and_labels(kd2)
                    steps.extend(kd1_steps)
                    labels.extend(kd1_labels)
                    steps.extend(kd2_steps)
                    labels.extend(kd2_labels)
                    action_result_dict.update(action_result)  # 更新动作结果里面动作里面的值
                    for rk, rv in action_result.items():
                        if rk in question_eq_converts:
                            af_steps, af_labels = extract_af_steps_and_labels(af)
                            steps.extend(af_steps)
                            labels.extend(af_labels)
                            result = (rk, rv)
                            finished = True
                            break
                except Exception:
                    pass
        self.steps.extend(steps)
        for label in labels:
            self.label.add(label)
        if not result:  # 如果不能按照之前的架构不能得出，结果那么有可能是那种 问题跟条件联系紧密的类型,所以我们可以把已经得出的结果，进行代换
            self.steps.append(['推导可得:', BaseEqs(action_result_dict.items()).printing()])
            _result = question_f.subs(action_result_dict.items())
            if _result.is_real:
                self.steps.append(['所以:', BaseEq([question_f, _result]).printing()])
                self.output.append(BaseSymbolValue({question_f: _result}))
                return self
            else:
                raise Exception('Try Error')
        self.steps.append(['\\therefore ' + BaseEq(result).printing(), ''])
        f_map = question_eq_converts[result[0]]
        f_value = f_map(result[1])
        self.steps.append(['\\therefore ' + new_latex(question_f), '=' + new_latex(f_value)])
        self.output.append(BaseSymbolValue({question_f: f_value}))
        self.steps.append(['求得:', new_latex(question_f) + '=' + new_latex(f_value)])
        return self


# 已知一个条件求值
class SanJiaoXingQiuZhi004(BaseFunction):
    def solver(self, *args):
        self.label.add('解三角形')
        cond_eqs = args[0].sympify()
        if len(cond_eqs) != 1:
            raise Exception('Type Match Error')
        question_f = args[1].sympify()
        question_f = question_f.subs(san_utils.get_triangle_subs())
        question_eq_converts = question_convets(question_f)
        question_eq_converts = dict(map(lambda _: (sympify(str(_[0])), _[1]), question_eq_converts.items()))
        eq = cond_eqs[0]
        cond_eq_converts, kds = condition_deep_deduce_v2(BaseEq(eq))
        finished = False
        result = None
        action_result_dict = {}
        steps = []
        labels = []
        for cond_eq, kd in zip(cond_eq_converts, kds):
            if finished:
                break
            for action_func in one_cond_action_funcs:
                try:
                    action_result, af = action_func(BaseEqs([cond_eq, ]))
                    if not action_result:
                        continue
                    action_result_dict.update(action_result)
                    for rk, rv in action_result.items():
                        rk = sympify(str(rk))
                        if rk in question_eq_converts:
                            kd_steps, kd_labels = extract_kd_steps_and_labels(kd)
                            steps.extend(kd_steps)
                            labels.extend(kd_labels)
                            af_steps, af_labels = extract_af_steps_and_labels(af)
                            steps.extend(af_steps)
                            labels.extend(af_labels)
                            result = (rk, rv)
                            finished = True
                            break
                except Exception:
                    pass
        self.steps.extend(steps)
        for label in labels:
            self.label.add(label)
        if not result:
            self.steps.append(['推导可得:', BaseEqs(action_result_dict.items()).printing()])
            _result = question_f.subs(action_result_dict.items())
            if _result.is_real:
                self.steps.append(['所以:', BaseEq([question_f, _result]).printing()])
                self.output.append(BaseSymbolValue({question_f: _result}))
                return self
            else:
                raise Exception('Try Error')
        self.steps.append(['\\therefore ' + BaseEq(result).printing(), ''])
        f_map = question_eq_converts[result[0]]
        f_value = f_map(result[1])
        self.steps.append(['\\therefore ' + new_latex(question_f), '=' + new_latex(f_value)])
        self.output.append(BaseSymbolValue({question_f: f_value}))
        return self


class SanJiaoXingQiuZhi(BaseFunction):
    CLS = [SanJiaoXingQiuZhi003, SanJiaoXingQiuZhi004, SanJiaoXingQiuZhi001, SanJiaoXingQiuZhi002]

    def solver(self, *args):
        known_eqs = san_utils.known_triangle_eqs(self.known)
        if known_eqs:
            tmp_args = [known_eqs, ]
        else:
            tmp_args = []
        tmp_args.extend([arg for arg in args])
        solve_r = None
        for cl in SanJiaoXingQiuZhi.CLS:
            try:
                solve_r = cl(verbose=True)
                solve_r.known = self.known
                solve_r = solve_r.solver(*tmp_args)
                solve_r.label.add('解三角形')
                break
            except Exception:
                solve_r = None
        if not solve_r:
            raise Exception('Can not solve the triangle')
        return solve_r


if __name__ == '__main__':
    pass
